﻿/* 2012/8/24 */
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Linq;
using Cosmos.Collections;
using Cosmos.Security.Cryptography;

namespace Cosmos.Windows.Forms.FileList
{
    /// <summary>
    /// Represents a Windows list view control, which displays a collection of file names and their information.
    /// </summary>
    [DefaultEvent("SelectedChanged")]
    public partial class FileInfoListView : UserControl, IFileList
    {
        /// <summary>
        /// Represents an item in FileInfoListView control.
        /// </summary>
        class FileInfoListViewItem : ListViewItem, IFileListItem
        {
            private HashValue _hashValue;

            private static string[] ColumnNames = new string[] { 
                _fileNameColumnName, _fileDirectoryColumnName, _fileNameCodeColumnName,
                _fileCreationTime, _fileLastAccessTime, _fileLastWriteTime,
                _fileMD5ColumnName, _fileSHA1ColumnName, _fileCRC32ColumnName 
            };

            /// <summary>
            /// Initializes a new instance of FileInfoListViewItem class.
            /// </summary>
            /// <param name="path">The path represented by current item.</param>
            public FileInfoListViewItem(string path)
            {
                SubItems.Clear();
                ListViewItem.ListViewSubItem subItem;
                for (int i = 0; i < ColumnNames.Length; i++)
                {
                    subItem = new ListViewItem.ListViewSubItem();
                    subItem.Name = ColumnNames[i];
                    if (i == 0)
                        SubItems[0] = subItem;
                    else
                        SubItems.Add(subItem);
                }

                FilePath = path;
                _hashValue = null;

                ConvertName();
            }

            /// <summary>
            /// Makes the ListView control draw the item again.
            /// </summary>
            public void Update()
            {
                bool isSelected = this.Selected;
                this.Selected = !isSelected;
                this.Selected = isSelected;
            }

            /// <summary>
            /// Calculates the hash value of the file represented by current item.
            /// </summary>
            public void HashFile()
            {
                if (HasHashed)
                    return;

                string filePath = FilePath;
                if (File.Exists(filePath))
                {
                    HashValue fileHashValue = FileHashCalculator.HashFile(filePath);
                    FileHashValue = fileHashValue;
                }
            }

            /// <summary>
            /// Converts the text of name column and display at name code column.
            /// </summary>
            private void ConvertName()
            {
                StringBuilder strBuilder = new StringBuilder();
                foreach (char aChar in SubItems[_fileNameColumnName].Text)
                {
                    if (Char.GetUnicodeCategory(aChar) == UnicodeCategory.OtherLetter)
                    {
                        int charCode = aChar;
                        strBuilder.Append("|");
                        strBuilder.Append(charCode.ToString("x"));
                        strBuilder.Append("|");
                    }
                    else if (aChar.Equals(' '))
                        strBuilder.Append("|*|");
                    else if (aChar.Equals('　'))
                        strBuilder.Append("|＊|");
                    else
                        strBuilder.Append(aChar);
                }
                SubItems[_fileNameCodeColumnName].Text = strBuilder.ToString();
            }

            /// <summary>
            /// Gets or sets the path represented by this item.
            /// </summary>
            public string FilePath
            {
                get
                {
                    return Path.Combine(SubItems[_fileDirectoryColumnName].Text,
                        SubItems[_fileNameColumnName].Text);
                }

                set
                {
                    SubItems[_fileNameColumnName].Text = Path.GetFileName(value);
                    SubItems[_fileDirectoryColumnName].Text = Path.GetDirectoryName(value);
                    ToolTipText = value;
                    if (File.Exists(value))
                    {
                        FileInfo file = new FileInfo(value);
                        SubItems[_fileCreationTime].Text = file.CreationTime.ToString();
                        SubItems[_fileLastAccessTime].Text = file.LastAccessTime.ToString();
                        SubItems[_fileLastWriteTime].Text = file.LastWriteTime.ToString();
                    }
                    ConvertName();
                }
            }

            /// <summary>
            /// Gets or sets the hash value of the file represented by this item.
            /// </summary>
            public HashValue FileHashValue
            {
                get
                {
                    if (!HasHashed)
                        HashFile();

                    return _hashValue;
                }

                set
                {
                    _hashValue = value;
                    SubItems[_fileMD5ColumnName].Text = _hashValue.MD5Text;
                    SubItems[_fileSHA1ColumnName].Text = _hashValue.SHA1Text;
                    SubItems[_fileCRC32ColumnName].Text = _hashValue.CRC32Text;
                }
            }

            /// <summary>
            /// Gets a value determining whether the hash value has been calculated.
            /// </summary>
            public bool HasHashed
            {
                get
                {
                    return ((object)_hashValue) != null;
                }
            }
        }

        class HashResult
        {
            public string FilePath { get; set; }
            public HashValue FileHashValue { get; set; }
        }

        #region Constants

        private const string _fileNameColumnName = "FileName";
        private const string _fileDirectoryColumnName = "FileDirectory";
        private const string _fileNameCodeColumnName = "FileNameCode";
        private const string _fileCreationTime = "FileCreationTime";
        private const string _fileLastAccessTime = "FileLastAccessTime";
        private const string _fileLastWriteTime = "FileLastWriteTime";
        private const string _fileMD5ColumnName = "FileMD5";
        private const string _fileSHA1ColumnName = "FileSHA1";
        private const string _fileCRC32ColumnName = "FileCRC32";

        #endregion

        #region Fields

        /// <summary>
        /// The items of fileThumbListView are being selected by some method, and the event 'SelectedIndexChanged' should do nothing.
        /// </summary>
        private bool _isSelectingListView;
        /// <summary>
        /// Prepare to rename a file. When the theItem in listView is renamed, event 'SelectedIndexChanged' should do nothing.
        /// </summary>
        private bool _isRenamingFile;

        /// <summary>
        /// The file paths which can be retrieved.
        /// </summary>
        private List<string> _pathList;

        /// <summary>
        /// The hash set which contains added paths.
        /// </summary>
        private HashSet<string> _pathSet;

        /// <summary>
        /// The item cache of list view using virtual mode.
        /// </summary>
        private Dictionary<string, FileInfoListViewItem> _itemCache;

        private SwitchableCollection<string> _hashTasks;

        #endregion

        /// <summary>
        /// Initializes a new instance of FileInfoListView class.
        /// </summary>
        public FileInfoListView()
        {
            InitializeComponent();

            listView.VirtualMode = true;
            listView.VirtualListSize = 0;
            listView.View = View.Details;
            listView.GridLines = true;
            listView.AllowColumnReorder = true;
            listView.FullRowSelect = true;
            listView.ShowItemToolTips = true;
            listView.HideSelection = false;
            listView.Columns.Add(_fileNameColumnName, "文件名");
            listView.Columns.Add(_fileDirectoryColumnName, "所在文件夹");
            listView.Columns.Add(_fileNameCodeColumnName, "文件名编码值");
            listView.Columns.Add(_fileCreationTime, "创建时间");
            listView.Columns.Add(_fileLastAccessTime, "访问时间");
            listView.Columns.Add(_fileLastWriteTime, "修改时间");
            listView.Columns.Add(_fileMD5ColumnName, "MD5");
            listView.Columns.Add(_fileSHA1ColumnName, "SHA1");
            listView.Columns.Add(_fileCRC32ColumnName, "CRC32");

            _pathList = new List<string>();
            _pathSet = new HashSet<string>();
            _itemCache = new Dictionary<string, FileInfoListViewItem>();
            _hashTasks = new SwitchableCollection<string>() { Mode = SwitchableCollectionMode.Queue };

            _isSelectingListView = false;
            _isRenamingFile = false;
        }

        #region Hash Methods

        /// <summary>
        /// Cancel asynchronous hash calculation.
        /// </summary>
        public void CancelHash()
        {
            lock (_hashTasks)
            {
                _hashTasks.Clear();
            }
        }

        /// <summary>
        /// Calculates hash values of all files in the list.
        /// </summary>
        public void HashAllFile()
        {
            lock (_hashTasks)
            {
                lock (_pathList)
                {
                    foreach (string aPath in _pathList)
                    {
                        _hashTasks.Store(aPath);
                    }
                }
            }

            StartHashAsync();
        }

        /// <summary>
        /// Calculates hash values of selected files in the list.
        /// </summary>
        public void HashSelectedFile()
        {
            lock (_hashTasks)
            {
                lock (_pathList)
                {
                    foreach (int anIndex in SelectedIndices)
                    {
                        _hashTasks.Store(_pathList[anIndex]);
                    }
                }
            }

            StartHashAsync();
        }

        public HashValue GetHash(int index)
        {
            if (index < 0 || index >= Count)
                return null;

            FileInfoListViewItem item = (FileInfoListViewItem)listView.Items[index];
            return item.FileHashValue;
        }

        public HashValue GetHash(string path)
        {
            int index = IndexOf(path);
            return GetHash(index);
        }

        public void SetBackColor(int index, Color color)
        {
            if (index < 0 || index >= Count)
                return;

            FileInfoListViewItem item = (FileInfoListViewItem)listView.Items[index];
            item.BackColor = color;
        }

        public void SetBackColor(string path, Color color)
        {
            int index = IndexOf(path);
            SetBackColor(index, color);
        }

        public void SetForeColor(int index, Color color)
        {
            if (index < 0 || index >= Count)
                return;

            FileInfoListViewItem item = (FileInfoListViewItem)listView.Items[index];
            item.ForeColor = color;
        }

        public void SetForeColor(string path, Color color)
        {
            int index = IndexOf(path);
            SetForeColor(index, color);
        }

        private void StartHashAsync()
        {
            if (!hashBackgroundWorker.IsBusy)
            {
                OnHashCalculating(EventArgs.Empty);
                hashBackgroundWorker.RunWorkerAsync();
            }
        }

        #endregion

        #region FileList Methods

        public void SelectPrevious()
        {
            IsSelectingListView = true;
            if (listView.SelectedIndices.Count == 0)
            {
                int listSize = Count;
                if (listSize == 0)
                    return;

                listView.SelectedIndices.Add(listSize - 1);
                listView.EnsureVisible(listSize - 1);
            }
            else
            {
                int fileIndex = listView.SelectedIndices[0];
                listView.SelectedIndices.Clear();
                int listSize = Count;
                fileIndex = (--fileIndex + listSize) % listSize;
                listView.SelectedIndices.Add(fileIndex);
                listView.EnsureVisible(fileIndex);
            }
            IsSelectingListView = false;

            OnSelectedChanged(EventArgs.Empty);
        }

        public void SelectNext()
        {
            IsSelectingListView = true;
            if (listView.SelectedIndices.Count == 0)
            {
                int listSize = Count;
                if (listSize == 0)
                    return;

                listView.SelectedIndices.Add(0);
                listView.EnsureVisible(0);
            }
            else
            {
                int fileIndex = listView.SelectedIndices[0];
                listView.SelectedIndices.Clear();
                int listSize = Count;
                fileIndex = (++fileIndex) % listSize;
                listView.SelectedIndices.Add(fileIndex);
                listView.EnsureVisible(fileIndex);
            }
            IsSelectingListView = false;

            OnSelectedChanged(EventArgs.Empty);
        }

        public void SelectNone()
        {
            IsSelectingListView = true;
            listView.BeginUpdate();
            listView.SelectedIndices.Clear();
            listView.EndUpdate();
            IsSelectingListView = false;

            OnSelectedChanged(EventArgs.Empty);
        }

        public void SelectAll()
        {
            IsSelectingListView = true;
            listView.BeginUpdate();
            int listSize = Count;
            for (int i = 0; i < listSize; i++)
            {
                listView.SelectedIndices.Add(i);
            }
            listView.EndUpdate();
            IsSelectingListView = false;

            OnSelectedChanged(EventArgs.Empty);
        }

        public void SelectInvert()
        {
            IsSelectingListView = true;
            listView.BeginUpdate();
            int listSize = Count;
            for (int i = 0; i < listSize; i++)
            {
                if (listView.SelectedIndices.Contains(i))
                    listView.SelectedIndices.Remove(i);
                else
                    listView.SelectedIndices.Add(i);
            }
            listView.EndUpdate();
            IsSelectingListView = false;

            OnSelectedChanged(EventArgs.Empty);
        }

        public int GetSelectedIndex()
        {
            if (listView.SelectedIndices.Count != 0)
                return listView.SelectedIndices[0];
            else
                return -1;
        }

        public int[] GetSelectedIndices()
        {
            int count = listView.SelectedIndices.Count;
            int[] selectedIndices = new int[count];
            for (int i = 0; i < count; i++)
            {
                selectedIndices[i] = listView.SelectedIndices[i];
            }
            return selectedIndices;
        }

        public string GetSelectedPath()
        {
            if (listView.SelectedIndices.Count != 0)
                return _pathList[listView.SelectedIndices[0]];
            else
                return null;
        }

        public string[] GetSelectedPaths()
        {
            int count = listView.SelectedIndices.Count;
            string[] selectedPaths = new string[count];
            for (int i = 0; i < count; i++)
            {
                selectedPaths[i] = _pathList[listView.SelectedIndices[i]];
            }
            return selectedPaths;
        }

        public void SetSelectedIndex(int index)
        {
            SelectIndex(index, true);
            EnsureVisible(index);

            OnSelectedChanged(EventArgs.Empty);
        }

        public void SetSelectedIndices(int[] indices)
        {
            SelectIndices(indices, true);
            if (indices.Length > 0)
                EnsureVisible(indices[0]);

            OnSelectedChanged(EventArgs.Empty);
        }

        public void SetSelectedPath(string path)
        {
            SelectPath(path, true);
            EnsureVisible(path);

            OnSelectedChanged(EventArgs.Empty);
        }

        public void SetSelectedPaths(string[] paths)
        {
            SelectPaths(paths, true);
            if (paths.Length > 0)
                EnsureVisible(paths[0]);

            OnSelectedChanged(EventArgs.Empty);
        }

        public string GetPathAt(int index)
        {
            if (index >= 0 && index < Count)
                return _pathList[index];
            else
                return null;
        }

        public int IndexOf(string path)
        {
            int index = -1;
            int fileCount = _pathList.Count;
            for (int i = 0; i < fileCount; i++)
            {
                if (_pathList[i].Equals(path))
                {
                    index = i;
                    break;
                }
            }
            return index;
        }

        public bool Contains(string path)
        {
            return _pathSet.Contains(path);
        }

        public void Clear()
        {
            if (Count > 0)
                listView.EnsureVisible(0);
            listView.VirtualListSize = 0;

            _pathSet.Clear();
            _pathList.Clear();
            _itemCache.Clear();
            OnPathCleared(EventArgs.Empty);
        }

        public int Add(string path)
        {
            int theIndex = -1;
            if (path != null && !Contains(path))
            {
                _pathList.Add(path);
                listView.VirtualListSize++;
                theIndex = listView.VirtualListSize - 1;
                _pathSet.Add(path);
                OnPathAdded(new PathEventArgs(path));
            }
            return theIndex;
        }

        public void AddRange(IEnumerable<string> paths)
        {
            int fileCount = 0;
            List<string> addedPathList = null;
            if (paths is ICollection<string>)
            {
                fileCount = ((ICollection<string>)paths).Count;
                int capacity = _pathList.Capacity + fileCount;
                if (_pathList.Capacity < capacity)
                    _pathList.Capacity = capacity;
                addedPathList = new List<string>(fileCount);
            }
            else
            {
                addedPathList = new List<string>();
            }

            listView.BeginUpdate();
            foreach (string aPath in paths)
            {
                if (aPath != null && !Contains(aPath))
                {
                    _pathList.Add(aPath);
                    _pathSet.Add(aPath);
                    addedPathList.Add(aPath);
                }
            }
            listView.VirtualListSize = _pathList.Count;
            listView.EndUpdate();
            OnPathsAdded(new PathsEventArgs(addedPathList.ToArray()));
        }

        public int Insert(int index, string path)
        {
            int theIndex = -1;
            if ((index >= 0 && index < Count) && (path != null && !Contains(path)))
            {
                _pathList.Insert(index, path);
                listView.VirtualListSize++;
                theIndex = index;
                _pathSet.Add(path);
                OnPathInserted(new PathInsertedEventArgs(index, path));
            }
            return theIndex;
        }

        public int Remove(string path)
        {
            int theIndex = IndexOf(path);
            if (theIndex != -1)
            {
                _pathList.RemoveAt(theIndex);
                listView.VirtualListSize--;
                _pathSet.Remove(path);
                _itemCache.Remove(path);
                OnPathRemoved(new PathEventArgs(path));
            }
            return theIndex;
        }

        public int RemoveAt(int index)
        {
            int theIndex = -1;
            string path = null;
            if (index >= 0 && index < Count)
            {
                path = _pathList[index];
                _pathList.RemoveAt(index);
                listView.VirtualListSize--;
                theIndex = index;
                _pathSet.Remove(path);
                _itemCache.Remove(path);
                OnIndexRemoved(new IndexEventArgs(index));
            }
            return theIndex;
        }

        public int Rename(string srcPath, string dstPath)
        {
            int theIndex = -1;
            if (dstPath != null && !Contains(dstPath))
            {
                theIndex = IndexOf(srcPath);
                if (theIndex != -1)
                {
                    IsRenamingFile = true;
                    _pathList[theIndex] = dstPath;

                    _pathSet.Remove(srcPath);
                    _pathSet.Add(dstPath);

                    FileInfoListViewItem item = null;
                    if (_itemCache.TryGetValue(srcPath, out item))
                    {
                        _itemCache.Remove(srcPath);
                        item.FilePath = dstPath;
                        _itemCache.Add(dstPath, item);
                        listView.Refresh();
                    }

                    IsRenamingFile = false;
                    OnRenamed(new RenamedEventArgs(srcPath, dstPath));
                }
            }
            return theIndex;
        }

        public void EnsureVisible(int index)
        {
            if (index >= 0 && index < Count)
            {
                listView.EnsureVisible(index);
            }
        }

        public void EnsureVisible(string path)
        {
            EnsureVisible(IndexOf(path));
        }

        private void SelectIndex(int index, bool selected)
        {
            IsSelectingListView = true;
            int listSize = Count;            
            if (index >= 0 && index < listSize)
            {
                if (selected)
                    listView.SelectedIndices.Add(index);
                else
                    listView.SelectedIndices.Remove(index);
            }
            IsSelectingListView = false;
        }

        private void SelectIndices(IEnumerable<int> indices, bool selected)
        {
            if (indices == null)
                return;

            IsSelectingListView = true;
            int listSize = Count;
            listView.BeginUpdate();
            foreach (int anIndex in indices)
            {
                if (anIndex >= 0 && anIndex < listSize)
                    if (selected)
                        listView.SelectedIndices.Add(anIndex);
                    else
                        listView.SelectedIndices.Remove(anIndex);
            }
            listView.EndUpdate();
            IsSelectingListView = false;
        }

        private void SelectPath(string path, bool selected)
        {
            if (String.IsNullOrEmpty(path))
                return;

            IsSelectingListView = true;
            int listSize = Count;
            for (int i = 0; i < listSize; i++)
            {
                if (_pathList[i].Equals(path))
                {
                    if (selected)
                        listView.SelectedIndices.Add(i);
                    else
                        listView.SelectedIndices.Remove(i);

                    break;
                }
            }
            IsSelectingListView = false;
        }

        private void SelectPaths(IEnumerable<string> paths, bool selected)
        {
            if (paths == null)
                return;

            IsSelectingListView = true;
            List<string> pathList = new List<string>(paths);
            int listSize = Count;
            string currentPath = null;
            listView.BeginUpdate();
            for (int i = 0; i < listSize; i++)
            {
                currentPath = _pathList[i];
                if (pathList.Contains(currentPath))
                {
                    if (selected)
                        listView.SelectedIndices.Add(i);
                    else
                        listView.SelectedIndices.Remove(i);

                    pathList.Remove(currentPath);
                    if (pathList.Count == 0)
                        break;
                }
            }
            listView.EndUpdate();
            IsSelectingListView = false;
        }

        #endregion

        #region Event Trigger Methods

        protected void OnSelectedChanged(EventArgs e)
        {
            if (SelectedChanged != null)
                SelectedChanged(this, e);
        }

        protected void OnPathCleared(EventArgs e)
        {
            if (PathCleared != null)
                PathCleared(this, e);
        }

        protected void OnPathAdded(PathEventArgs e)
        {
            if (PathAdded != null)
                PathAdded(this, e);
        }

        protected void OnPathsAdded(PathsEventArgs e)
        {
            if (PathsAdded != null)
                PathsAdded(this, e);
        }

        protected void OnPathInserted(PathInsertedEventArgs e)
        {
            if (PathInserted != null)
                PathInserted(this, e);
        }

        protected void OnPathRemoved(PathEventArgs e)
        {
            if (PathRemoved != null)
                PathRemoved(this, e);
        }

        protected void OnIndexRemoved(IndexEventArgs e)
        {
            if (IndexRemoved != null)
                IndexRemoved(this, e);
        }

        protected void OnRenamed(RenamedEventArgs e)
        {
            if (Renamed != null)
                Renamed(this, e);
        }

        protected void OnHashCalculating(EventArgs e)
        {
            if (HashCalculating != null)
                HashCalculating(this, e);
        }

        protected void OnHashCalculated(EventArgs e)
        {
            if (HashCalculated != null)
                HashCalculated(this, e);
        }

        #endregion

        #region listView Event Handler Methods

        private void listView_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (IsSelectingListView || IsRenamingFile)
                return;

            OnSelectedChanged(EventArgs.Empty);
        }

        private void listView_CacheVirtualItems(object sender, CacheVirtualItemsEventArgs e)
        {
            int itemCount = e.EndIndex - e.StartIndex + 1;

            for (int i = 0; i < itemCount; i++)
            {
                string filePath = _pathList[e.StartIndex + i];
                if (!_itemCache.ContainsKey(filePath))
                {
                    FileInfoListViewItem item = new FileInfoListViewItem(filePath);
                    _itemCache.Add(filePath, item);
                }
            }
        }

        private void listView_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
        {
            ListView self = sender as ListView;
            string filePath = _pathList[e.ItemIndex];

            if (_itemCache.ContainsKey(filePath))
            {
                FileInfoListViewItem item = _itemCache[filePath];
                e.Item = item;
            }
            else
            {
                FileInfoListViewItem item = new FileInfoListViewItem(filePath);
                _itemCache.Add(filePath, item);
                e.Item = item;
            }
        }

        private void listView_SearchForVirtualItem(object sender, SearchForVirtualItemEventArgs e)
        {
            int listLength = _pathList.Count;
            for (int i = e.StartIndex; i < listLength - e.StartIndex; i++)
            {
                if (Path.GetFileName(_pathList[i]).Contains(e.Text))
                {
                    e.Index = i;
                    break;
                }
            }
        }

        #endregion

        #region hashBackgroundWorker Event Handler Methods

        private void hashBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;

            while (true)
            {
                string hashPath = null;

                lock (_hashTasks)
                {
                    if (_hashTasks.Count > 0)
                    {
                        hashPath = _hashTasks.Take();
                    }
                    else
                    {
                        break;
                    }
                }

                if (hashPath == null)
                    continue;

                lock (_pathSet)
                {
                    if (!_pathSet.Contains(hashPath))
                    {
                        continue;
                    }
                }

                bool hasHashed = false;
                lock (_itemCache)
                {
                    if (_itemCache.ContainsKey(hashPath))
                    {
                        FileInfoListViewItem item = _itemCache[hashPath];
                        if (item.HasHashed)
                        {
                            hasHashed = true;
                        }

                    }
                }

                if (!hasHashed)
                {
                    HashValue fileHashValue = FileHashCalculator.HashFile(hashPath);
                    worker.ReportProgress(0, new HashResult() { FilePath = hashPath, FileHashValue = fileHashValue });
                }
            }
        }

        private void hashBackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            HashResult hashResult = (HashResult)e.UserState;
            if (!Contains(hashResult.FilePath))
                return;

            FileInfoListViewItem item = null;
            lock (_itemCache)
            {
                if (_itemCache.TryGetValue(hashResult.FilePath, out item))
                {
                    item.FileHashValue = hashResult.FileHashValue;
                    listView.Refresh();
                }
                else
                {
                    item = new FileInfoListViewItem(hashResult.FilePath);
                    item.FileHashValue = hashResult.FileHashValue;
                    _itemCache.Add(hashResult.FilePath, item);
                }
            }
        }

        private void hashBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;

            int taskCount = 0;
            lock (_hashTasks)
            {
                taskCount = _hashTasks.Count;
            }

            if (taskCount > 0)
            {
                worker.RunWorkerAsync();
            }
            else
            {
                OnHashCalculated(EventArgs.Empty);
            }
        }

        #endregion

        #region FileList Properties

        [Browsable(false)]
        public int Count
        {
            get
            {
                return listView.Items.Count;
            }
        }

        [Browsable(false)]
        public string[] Paths
        {
            get
            {
                int itemCount = Count;
                string[] paths = new string[itemCount];
                for (int i = 0; i < itemCount; i++)
                {
                    FileInfoListViewItem theItem = (FileInfoListViewItem)listView.Items[i];
                    paths[i] = theItem.FilePath;
                }
                return paths;
            }
        }

        [Browsable(false)]
        [DefaultValue(-1)]
        public int SelectedIndex
        {
            get
            {
                return GetSelectedIndex();
            }
            set
            {
                int[] selectedIndices = GetSelectedIndices();
                SelectIndices(selectedIndices, false);
                SelectIndex(value, true);
                EnsureVisible(value);
                OnSelectedChanged(EventArgs.Empty);
            }
        }

        [Browsable(false)]
        [DefaultValue(new int[0])]
        public int[] SelectedIndices
        {
            get
            {
                return GetSelectedIndices();
            }
            set
            {
                int[] selectedIndices = GetSelectedIndices();
                if (value == null)
                {
                    SelectIndices(selectedIndices, false);
                }
                else
                {
                    SelectIndices(selectedIndices.Except(value), false);
                    SelectIndices(value.Except(selectedIndices), true);
                }
                OnSelectedChanged(EventArgs.Empty);
            }
        }

        [Browsable(false)]
        [DefaultValue(null)]
        public string SelectedPath
        {
            get
            {
                return GetSelectedPath();
            }
            set
            {
                string[] selectedPaths = GetSelectedPaths();
                SelectPaths(selectedPaths, false);
                SelectPath(value, true);
                EnsureVisible(value);
                OnSelectedChanged(EventArgs.Empty);
            }
        }

        [Browsable(false)]
        [DefaultValue(new string[0])]
        public string[] SelectedPaths
        {
            get
            {
                return GetSelectedPaths();
            }
            set
            {
                string[] selectedPaths = GetSelectedPaths();
                if (value == null)
                {
                    SelectPaths(selectedPaths, false);
                }
                else
                {
                    SelectPaths(selectedPaths.Except(value), false);
                    SelectPaths(value.Except(selectedPaths), true);
                }
                OnSelectedChanged(EventArgs.Empty);
            }
        }

        public string this[int index]
        {
            get
            {
                return GetPathAt(index);
            }
        }

        /// <summary>
        /// Prepare to rename a file. When the theItem in fileThumbListView is renamed, event 'SelectedIndexChanged' should do nothing.
        /// </summary>
        private bool IsRenamingFile
        {
            get { return _isRenamingFile; }
            set { _isRenamingFile = value; }
        }

        /// <summary>
        /// The items of fileThumbListView are being selected by some method, and the event 'SelectedIndexChanged' should do noting.
        /// </summary>
        private bool IsSelectingListView
        {
            get { return _isSelectingListView; }
            set { _isSelectingListView = value; }
        }

        #endregion

        #region Column Properties

        [Category("Column")]
        public int FileNameColumnWidth
        {
            get { return listView.Columns[_fileNameColumnName].Width; }
            set { listView.Columns[_fileNameColumnName].Width = value; }
        }

        [Category("Column")]
        public int FileDirectoryColumnWidth
        {
            get { return listView.Columns[_fileDirectoryColumnName].Width; }
            set { listView.Columns[_fileDirectoryColumnName].Width = value; }
        }

        [Category("Column")]
        public int FileNameCodeColumnWidth
        {
            get { return listView.Columns[_fileNameCodeColumnName].Width; }
            set { listView.Columns[_fileNameCodeColumnName].Width = value; }
        }

        [Category("Column")]
        public int FileCreationTimeColumnWidth
        {
            get { return listView.Columns[_fileCreationTime].Width; }
            set { listView.Columns[_fileCreationTime].Width = value; }
        }

        [Category("Column")]
        public int FileLastAccessTimeColumnWidth
        {
            get { return listView.Columns[_fileLastAccessTime].Width; }
            set { listView.Columns[_fileLastAccessTime].Width = value; }
        }

        [Category("Column")]
        public int FileLastWriteTimeColumnWidth
        {
            get { return listView.Columns[_fileLastWriteTime].Width; }
            set { listView.Columns[_fileLastWriteTime].Width = value; }
        }

        [Category("Column")]
        public int FileMD5ColumnWidth
        {
            get { return listView.Columns[_fileMD5ColumnName].Width; }
            set { listView.Columns[_fileMD5ColumnName].Width = value; }
        }

        [Category("Column")]
        public int FileSHA1ColumnWidth
        {
            get { return listView.Columns[_fileSHA1ColumnName].Width; }
            set { listView.Columns[_fileSHA1ColumnName].Width = value; }
        }

        [Category("Column")]
        public int FileCRC32ColumnWidth
        {
            get { return listView.Columns[_fileCRC32ColumnName].Width; }
            set { listView.Columns[_fileCRC32ColumnName].Width = value; }
        }

        [Category("Column")]
        public int FileNameColumnIndex
        {
            get { return listView.Columns[_fileNameColumnName].DisplayIndex; }
            set { listView.Columns[_fileNameColumnName].DisplayIndex = value; }
        }

        [Category("Column")]
        public int FileDirectoryColumnIndex
        {
            get { return listView.Columns[_fileDirectoryColumnName].DisplayIndex; }
            set { listView.Columns[_fileDirectoryColumnName].DisplayIndex = value; }
        }

        [Category("Column")]
        public int FileNameCodeColumnIndex
        {
            get { return listView.Columns[_fileNameCodeColumnName].DisplayIndex; }
            set { listView.Columns[_fileNameCodeColumnName].DisplayIndex = value; }
        }

        [Category("Column")]
        public int FileCreationTimeColumnIndex
        {
            get { return listView.Columns[_fileCreationTime].DisplayIndex; }
            set { listView.Columns[_fileCreationTime].DisplayIndex = value; }
        }

        [Category("Column")]
        public int FileLastAccessTimeColumnIndex
        {
            get { return listView.Columns[_fileLastAccessTime].DisplayIndex; }
            set { listView.Columns[_fileLastAccessTime].DisplayIndex = value; }
        }

        [Category("Column")]
        public int FileLastWriteTimeColumnIndex
        {
            get { return listView.Columns[_fileLastWriteTime].DisplayIndex; }
            set { listView.Columns[_fileLastWriteTime].DisplayIndex = value; }
        }

        [Category("Column")]
        public int FileMD5ColumnIndex
        {
            get { return listView.Columns[_fileMD5ColumnName].DisplayIndex; }
            set { listView.Columns[_fileMD5ColumnName].DisplayIndex = value; }
        }

        [Category("Column")]
        public int FileSHA1ColumnIndex
        {
            get { return listView.Columns[_fileSHA1ColumnName].DisplayIndex; }
            set { listView.Columns[_fileSHA1ColumnName].DisplayIndex = value; }
        }

        [Category("Column")]
        public int FileCRC32ColumnIndex
        {
            get { return listView.Columns[_fileCRC32ColumnName].DisplayIndex; }
            set { listView.Columns[_fileCRC32ColumnName].DisplayIndex = value; }
        }

        /// <summary>
        /// Gets or sets a string indicating the display indices and width values of columns.
        /// </summary>
        [Browsable(false)]
        public string ColumnSettings
        {
            get
            {
                StringWriter stringWriter = new StringWriter();
                XmlWriter xmlWriter = XmlWriter.Create(stringWriter);
                XDocument xDocument = new XDocument();
                XElement settingElement = new XElement("Columns");
                xDocument.Add(settingElement);
                
                foreach(ColumnHeader aColumnHeader in listView.Columns)
                {
                    XElement columnElement = new XElement(aColumnHeader.Name);
                    XAttribute indexAttribute = new XAttribute("index", aColumnHeader.DisplayIndex);
                    XAttribute widthAttribute = new XAttribute("width", aColumnHeader.Width);
                    columnElement.Add(indexAttribute, widthAttribute);
                    settingElement.Add(columnElement);
                }

                xDocument.Save(xmlWriter);
                xmlWriter.Flush();
                stringWriter.Flush();
                string result = stringWriter.ToString();
                xmlWriter.Close();
                stringWriter.Close();
                return result;
            }

            set
            {
                StringReader stringReader = new StringReader(value);
                XmlReader xmlReader = XmlReader.Create(stringReader);
                XDocument xDocument = XDocument.Load(xmlReader);
                XElement settingElement = xDocument.Element("Columns");

                foreach (XElement aElement in settingElement.Elements())
                {
                    ColumnHeader columnHeader = listView.Columns[aElement.Name.ToString()];
                    columnHeader.DisplayIndex = Int32.Parse(aElement.Attribute("index").Value);
                    columnHeader.Width = Int32.Parse(aElement.Attribute("width").Value);
                }

                xmlReader.Close();
                stringReader.Close();
            }
        }

        #endregion

        #region Hash Properties

        /// <summary>
        /// A value that determining whether the last requested hash will be 
        /// calculated firstly when there are many requested hash calculation.
        /// </summary>
        [Category("Hash")]
        [DefaultValue(false)]
        [Description("Determines whether the last requested hash will be calculated firstly when there are many requested hash calculation.")]
        public bool IsLastHashFirstCalculate
        {
            get
            {
                if (_hashTasks.Mode == SwitchableCollectionMode.Stack)
                    return true;
                else
                    return false;
            }

            set
            {
                if (value)
                {
                    _hashTasks.Mode = SwitchableCollectionMode.Stack;
                }
                else
                {
                    _hashTasks.Mode = SwitchableCollectionMode.Queue;
                }
            }
        }

        /// <summary>
        /// Gets a value indicating asynchronous hash calculation is running.
        /// </summary>
        [Browsable(false)]
        public bool IsHashing
        {
            get
            {
                return hashBackgroundWorker.IsBusy;
            }
        }

        #endregion

        #region FileList Events

        [Category("File List")]
        public event EventHandler SelectedChanged;

        [Category("File List")]
        public event EventHandler PathCleared;

        [Category("File List")]
        public event PathEventHandler PathAdded;

        [Category("File List")]
        public event PathsEventHandler PathsAdded;

        [Category("File List")]
        public event PathInsertedEventHandler PathInserted;

        [Category("File List")]
        public event PathEventHandler PathRemoved;

        [Category("File List")]
        public event IndexEventHandler IndexRemoved;

        [Category("File List")]
        public event RenamedEventHandler Renamed;

        #endregion

        #region Hash Events

        /// <summary>
        /// Occurs before asynchronous hash calculation runs.
        /// </summary>
        [Category("Hash")]
        public event EventHandler HashCalculating;

        /// <summary>
        /// Occurs after asynchronous hash calculation has run.
        /// </summary>
        [Category("Hash")]
        public event EventHandler HashCalculated;

        #endregion
    }
}
